﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Data;
using Communication;
using Data.Realm;
using System.Windows.Forms;
using System.Threading;
using System.Text.RegularExpressions;

namespace Atlantyda
{
    static public class Agent
    {
        static public bool locker = false;
        static private bool connected = false;
        static private AgentAPI api = new AgentAPI(Agent.Listen);
        static private AgentMap agent_map = new AgentMap();
        static private String address;
        static private Int16 port;
        static private String group_name;
        static private String password;
        static private String world_name;
        static private String agent_name;
        static private String web_address;
        static private WorldParameters world_parameters;
        static private MainForm mainForm;
        static private LoginForm loginForm;
        static private int actual_enegry = 0;
        static private int received_energy = 0;
        static private int used_energy = 0;
        static private Map map;
        static private OrientedField[] looked_field;
        static private int? height;
        static private int bad_automat_recharge = 0;
        static private Thread aim_thr;
        static private Thread map_thr;
        static private AimlBot aiml_bot = new AimlBot();

        static public AimlBot Aiml
        {
            get { return Agent.aiml_bot; }
            set { }
        }

        static public AgentMap AgentMap
        {
            get { return Agent.agent_map; }
            set { Agent.agent_map = value; }
        }
        
        static public Thread MapThr
        {
            get { return Agent.map_thr; }
            set { Agent.map_thr = value; }
        }

        static public Thread AimThr
        {
            get { return Agent.aim_thr; }
            set { Agent.aim_thr = value; }
        }

        static public int? Height
        {
            get { return Agent.height; }
            set { Agent.height = value; }
        }

        static public int BadAutomatRecharge
        {
            get { return Agent.bad_automat_recharge; }
            set { Agent.bad_automat_recharge = value; }
        }

        static public AgentAPI API
        {
            get { return Agent.api; }
            set { }
        }

        static public OrientedField[] LookedField
        {
            get { return Agent.looked_field; }
            set { Agent.looked_field = value; }
        }

        static public bool Connected
        {
            get { return Agent.connected; }
            set { }
        }

        static public void Disconnect()
        {
            MapThr.Abort();
            AimThr.Abort();
            connected = false;
            api.Disconnect();
            api = new AgentAPI(Listen);
            Height = null;
            Map.Clear();
            locker = false;
            MainForm.AgentConsole.Clear();
        }

        static public Map Map
        {
            get { return Agent.map; }
            set { Agent.map = value; }
        }

        static public int ActualEnergy
        {
            get { return Agent.actual_enegry; }
            set
            {
                int maxEnergy = WorldParameters.initialEnergy;
                int agentEnrgy = 0;
                if (value <= maxEnergy && value > 0)
                    agentEnrgy = value;
                else if (value > maxEnergy)
                    agentEnrgy = maxEnergy;
                Agent.actual_enegry = agentEnrgy;
                Agent.mainForm.healthBar.InvokeEx(bar => bar.Value = agentEnrgy);
                Agent.mainForm.healthLabel.InvokeEx(label => label.Text = agentEnrgy + " / " + WorldParameters.initialEnergy);
            }
        }

        static public int ReceivedEnergy
        {
            get { return Agent.received_energy; }
            set
            {
                Agent.received_energy = value;
                Agent.mainForm.receivedEnergyLabel.InvokeEx(label => label.Text = value.ToString());
            }
        }

        static public int UsedEnergy
        {
            get { return Agent.used_energy; }
            set
            { 
                Agent.used_energy = value;
                Agent.mainForm.usedEnergyLabel.InvokeEx(label => label.Text = value.ToString());
            }
        }

        static public LoginForm LoginForm
        {
            get { return Agent.loginForm; }
            set { Agent.loginForm = value; }
        }

        static public MainForm MainForm
        {
            get { return Agent.mainForm; }
            set { Agent.mainForm = value; }
        }

        static public String Address
        {
            set { Agent.address = value; }
            get { return Agent.address; }
        }

        static public String WebAddress
        {
            set { Agent.web_address = value; }
            get { return Agent.web_address; }
        }

        static public String GroupName
        {
            get { return Agent.group_name; }
            set
            {
                Agent.group_name = value;
                mainForm.groupNameLabel.Text = value;
            }
        }

        static public String WorldName
        {
            get { return Agent.world_name; }
            set
            {
                Agent.world_name = value;
                mainForm.worldNameLabel.Text = value;
            }
        }

        static public String AgentName
        {
            set
            {
                Agent.agent_name = value;
                mainForm.agentNameLabel.Text = value;
            }
            get { return Agent.agent_name; }
        }

        static public Int16 Port
        {
            get { return Agent.port; }
            set { Agent.port = value; }
        }

        static public String Password
        {
            get { return Agent.password; }
            set { Agent.password = value; }
        }

        static public WorldParameters WorldParameters
        {
            get { return Agent.world_parameters; }
            set
            {
                world_parameters = value;
                mainForm.maxRechargeLabel.Text = value.maxRecharge.ToString();
                mainForm.moveCostLabel.Text = value.moveCost.ToString();
                mainForm.rotateCostLabel.Text = value.rotateCost.ToString();
                mainForm.speakCostLabel.Text = value.speakCost.ToString();
            }
        }

        static public void Listen(String sender, String message)
        {
            if (sender != Agent.AgentName + "." + Agent.GroupName)
            {
                AddConsoleInformation(sender + ": " + message);
                if (Regex.IsMatch(sender, Agent.GroupName) && ActualEnergy > 200)
                {
                    locker = true;
                    Thread aiml = new Thread(delegate()
                    {
                        Aiml.Chat(sender, message);
                    });
                    aiml.Start();
                }
                else
                    locker = false;
            }
        }

        static public void Speak(string response)
        {
            AddConsoleInformation(Agent.AgentName + "." + Agent.GroupName + ": " + response);
            API.Speak(response, 1);
            ActualEnergy -= WorldParameters.speakCost;
        }

        static public bool Connect()
        {
            WorldParameters = api.Connect(Agent.Address, Agent.Port, Agent.GroupName, Agent.Password, Agent.WorldName, Agent.AgentName);
            connected = api.Connected;
            return api.Connected;
        }

        static public void ShowMap()
        {
            MainForm mainForm = MainForm;
            mainForm.healthBar.Maximum = WorldParameters.initialEnergy;
            ActualEnergy = WorldParameters.initialEnergy;
            mainForm.AgentPanel.Visible = true;
            mainForm.DisconnectMenuItem.Visible = true;
            mainForm.ConnectMenuItem.Visible = false;
            AddConsoleInformation("Połączony! Witamy na Atlantydzie!");
            UpdatingMap();
        }

        public static void RunBot()
        {
            ThreadStart thSt = new ThreadStart(AimBot);
            AimThr = new Thread(thSt);
            AimThr.Start();
        }
    
        static private void AimBot()
        {
            AimBot aim = new AimBot();
            aim.StartAim();
            Thread.CurrentThread.Abort();
        }

        public static int CompareOrientFieldByEnergy(OrientedField x, OrientedField y)
        {
            if (x == y)
                return 0;
            else if (x.energy == -1)
                return -1;
            else if (y.energy == -1)
                return 1;
            else if (x.energy > y.energy)
                return -1;
            else
                return 1;
        }

        public static OrientedField[] FindEnergy()
        {
            List<OrientedField> result = new List<OrientedField>();
            foreach(OrientedField field in Agent.LookedField)
            {
                if (field.energy > 0 || field.energy == -1)
                    result.Add(field);
            }
            result.Sort(CompareOrientFieldByEnergy);
            return result.ToArray();
        }

        static public void AddConsoleInformation(String text)
        {
            MainForm mainForm = Agent.MainForm;
            if (mainForm.AgentConsole.Text.Length > 0)
            {
                mainForm.AgentConsole.InvokeEx(console => console.AppendText(Environment.NewLine));
            }
            mainForm.AgentConsole.InvokeEx(console => console.AppendText(DateTime.Now.ToString("HH:mm:ss tt") + ": " + text));
        }

        public static int GetMoveCost()
        {
            OrientedField beforeField = Agent.GetOrientateField(0, 1);
            if (beforeField == null)
                return WorldParameters.moveCost;
            else
            {
                //int cost = WorldParameters.moveCost * (1 + (beforeField.height - Agent.Height.Value)/100;
                int cost = Convert.ToInt32(Math.Ceiling(Convert.ToDouble(WorldParameters.moveCost * (beforeField.height - Agent.Height.Value)) / 100));
                cost += WorldParameters.moveCost;
                if (ActualEnergy > cost) Height = beforeField.height;
                return cost;
            }
        }

        public static OrientedField GetOrientateField(int x, int y)
        {
            foreach (OrientedField field in Agent.LookedField)
            {
                if (field.x == x && field.y == y)
                    return field;
            }
            return null;
        }

        public static void RotateLeft()
        {
            if (!API.RotateLeft())
                AddConsoleInformation("Obrot nie powiodl sie - brak energii");
            else
            {
                ActualEnergy -= WorldParameters.rotateCost;
                UsedEnergy -= WorldParameters.rotateCost;
                agent_map.setOrientation(0);
                agent_map.GetActualField().LastRotate.Add("left");
            }
        }

        public static void RotateRight()
        {
            if (!API.RotateRight())
                AddConsoleInformation("Obrot nie powiodl sie - brak energii");
            else
            {
                ActualEnergy -= WorldParameters.rotateCost;
                UsedEnergy -= WorldParameters.rotateCost;
                agent_map.setOrientation(1);
                agent_map.GetActualField().LastRotate.Add("right");
            }
        }

        public static int StepForward()
        {
            int moveCost = GetMoveCost();
            if (API.StepForward())
            {
                ActualEnergy -= moveCost;
                UsedEnergy += moveCost;
                agent_map.StepForward();
                return 0;
            }
            else if (ActualEnergy < moveCost)
            {
                AddConsoleInformation("Wykonanie kroku nie powiodlo sie - brak energii.");
                return 1;
            }
            else
            {
                ActualEnergy -= moveCost;
                UsedEnergy += moveCost;
                AddConsoleInformation("Wykonanie kroku nie powiodlo sie - agent natrafił na ścianę, przeszkodę lub innego agenta.");
                return 2;
            }
        }

        public static void Recharge()
        {
            int added = 0;
            int energy;
            while ((energy = API.Recharge()) > 0)
            {
                added += energy;
            }
            if (added > 0)
            {
                ActualEnergy += added;
                ReceivedEnergy += added;
                agent_map.GetEnergyFromField(added);
                AddConsoleInformation("Otrzymano " + added + " energii");
                Aiml.EndConversation = null;
            }
            else
            {
                AddConsoleInformation("Nie otrzymano żadnej energii");
            }
        }

        public static void Look()
        {
            try
            {
                LookedField = API.Look();
                string energy, obstacle, agent;
                foreach (OrientedField field in LookedField)
                {
                    energy = obstacle = agent = "";
                    if (field.agent != null && field.agent.agentGroup == GroupName && Aiml.LastCallerName == null && Agent.AgentMap.GetActualField().Height > field.height && Agent.ActualEnergy > 200)
                        Aiml.StartConversation(field.agent.agentname);
                    else
                        locker = false;
                    if (field.energy != 0)
                        energy = " | Energia: " + field.energy.ToString();
                    if (field.obstacle)
                        obstacle = " | Przeszkoda";
                    if (field.agent != null)
                        agent = " | Agent " + field.agent.fullName + " i jest obrocony na " + field.agent.direction.ToString();
                    //AddConsoleInformation("POLE " + field.x + "," + field.y + " | Wysokość: " + field.height + energy + obstacle + agent);
                    agent_map.AddLookField(field.x, field.y, field.height, field.energy, field.obstacle);
                    mainForm.lookedFieldsLabel.InvokeEx(lookedFieldsLabel => lookedFieldsLabel.Text = Agent.AgentMap.Fields.Count().ToString());
                }
            }
            catch (TimeoutException ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        public static void UpdatingMap()
        {
            ThreadStart thSt = new ThreadStart(UpdateThread);
            MapThr = new Thread(thSt);
            MapThr.Start();
        }

        private static void UpdateThread()
        {
            Map = new Map();
            Map.GetWorld();
            while (Connected)
            {
                Map.GetUpdate();
            }
            Thread.CurrentThread.Abort();
        }

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            MainForm mainForm = new MainForm();
            Agent.MainForm = mainForm;
            Application.Run(mainForm);
        }
    }
}
